/*  This file is part of -_-.

    -_- is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    -_- is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.

    You should have received a copy of the GNU General Public License
    along with -_-.  If not, see <http://www.gnu.org/licenses/>.

    Copyright 2012-2013 Oliver Katz */

#ifndef __VM_BACKEND_H
#define __VM_BACKEND_H

#include <iostream>
#include <string>
#include <vector>
#include <sstream>

#include <DerivedTypes.h>
#include <LLVMContext.h>
#include <Module.h>
#include <PassManager.h>
#include <Verifier.h>
#include <Passes.h>
#include <ExecutionEngine.h>
#include <Transforms/Scalar.h>
#include <IRBuilder.h>
#include <TargetSelect.h>
#include <JIT.h>

//namespace sleepyface::vm
namespace sleepyface
{
	namespace vm
	{
		using namespace std;
		using namespace llvm;

		class VM;

		//class LiteralType: constant value for types
		class LiteralType
		{
		private:
			int val;
			int elem;

		public:
			const static int _uninitialized = -1; //memberof LiteralType: null type
			const static int _char = 0; //memberof LiteralType: 8-bit integral type
			const static int _int = 1; //memberof LiteralType: 32-bit integral type
			const static int _double = 2; //memberof LiteralType: 64-bit floating-point type
			const static int _addr = 3; //memberof LiteralType: 32-bit pointer type

			LiteralType(); //methodof LiteralType: initializer
			LiteralType(int v); //methodof LiteralType: initializer with type value
			LiteralType(int v, int e); //methodof LiteralType: initializer with type value and that of element (for _addr)
			bool initialized(); //methodof LiteralType: returns false only if uninitialized (i.e. from LiteralType())
			int get(); //methodof LiteralType: get type value
			int getElement(); //methodof LiteralType: get element type value
			Type *gen(VM &vm); //methodof LiteralType: generate LLVM type
			string display(string pref); //methodof LiteralType: display with prefix 'pref'
			string display(); //methodof LiteralType: just display
		};

		//class LiteralChar: constant value for characters (8-bit integers)
		class LiteralChar
		{
		private:
			bool init;
			char val;

		public:
			LiteralChar(); //methodof LiteralChar: initializer
			LiteralChar(char v); //methodof LiteralChar: initializer with value
			bool initialized(); //methodof LiteralChar: returns true if has been initialized with value (not based of value)
			char get(); //methodof LiteralChar: gets value
			Value *gen(VM &vm); //methodof LiteralChar: generates LLVM value
			string display(string pref); //methodof LiteralChar: display with prefix 'pref'
			string display(); //methodof LiteralChar: just display
		};

		//class LiteralInt: constant value for integers (32-bit) - methods are same as LiteralChar
		class LiteralInt
		{
		private:
			bool init;
			int val;

		public:
			LiteralInt();
			LiteralInt(int v);
			bool initialized();
			int get();
			Value *gen(VM &vm);
			string display(string pref);
			string display();
		};

		//class LiteralDouble: constant value for doubles (64-bit floating-point) - methods are same as LiteralChar, LiteralInt
		class LiteralDouble
		{
		private:
			bool init;
			double val;

		public:
			LiteralDouble();
			LiteralDouble(double v);
			bool initialized();
			double get();																																																																																																																																																																																																																				
			Value *gen(VM &vm);
			string display(string pref);
			string display();
		};

		//class LiteralAddress: constant value for addresses (32-bit pointers) - methods are same as LiteralChar, LiteralInt, LiteralDouble
		class LiteralAddress
		{
		private:
			bool init;
			LiteralType elem;
			void *val;

		public:
			LiteralAddress();
			LiteralAddress(void *v);
			LiteralAddress(LiteralType e, void *v);
			bool initialized();
			LiteralType getElementType();
			void *get();
			Value *gen(VM &vm);
			string display(string pref);
			string display();
		};

		//class LiteralValue: typed value than can be any literal
		class LiteralValue
		{
		private:
			shared_ptr<LiteralType> type;
			shared_ptr<LiteralChar> _char;
			shared_ptr<LiteralInt> _int;
			shared_ptr<LiteralDouble> _double;
			shared_ptr<LiteralAddress> _addr;
			string name;

		public:
			LiteralValue(); //methodof LiteralValue: initializer
			LiteralValue(LiteralChar v); //methodof LiteralValue: initializer with LiteralChar
			LiteralValue(LiteralInt v); //methodof LiteralValue: initializer with LiteralInt
			LiteralValue(LiteralDouble v); //methodof LiteralValue: initializer with LiteralDouble
			LiteralValue(LiteralAddress v); //methodof LiteralValue: initializer with LiteralAddress
			LiteralValue(string v); //methodof LiteralValue: initiailizer with variable symbol
			bool initialized(); //methodof LiteralValue: checks to see if the value has been initialized
			bool isNamedVariable(); //methodof LiteralValue: checks to see if the value is a named variable reference
			string getName(); //methodof LiteralValue: get named variable reference
			LiteralType getType(); //methodof LiteralValue: get LiteralType of value
			LiteralChar getChar(); //methodof LiteralValue: get LiteralChar without casting (may fail - use castChar)
			LiteralInt getInt(); //methodof LiteralValue: get LiteralInt without casting (may fail - use castInt)
			LiteralDouble getDouble(); //methodof LiteralValue: get LiteralDouble without casting (may fail - use castDouble)
			LiteralAddress getAddress(); //methodof LiteralValue: get LiteralAddress without casting (may fail - use castAddress)
			LiteralChar castChar(); //methodof LiteralValue: get LiteralChar with casting
			LiteralInt castInt(); //methodof LiteralValue: get LiteralInt with casting
			LiteralDouble castDouble(); //methodof LiteralValue: get LiteralDouble with casting
			LiteralAddress castAddress(); //methodof LiteralValue: get LiteralAddress with casting
			Value *gen(VM &vm); //methodof LiteralValue: generates LLVM value
			string display(string pref); //methodof LiteralValue: displays with prefix 'pref'
			string display(); //methodof LiteralValue: just displays
		};

		class MutableDefinition;
		class MutableAssignment;
		class Operation;
		class Call;
		class IfStatement;
		class ForLoop;

		//class FunctionalNode: can be any type of expression
		class FunctionalNode
		{
		public:
			const static int _uninitialized = -1; //memberof FunctionalNode: null type
			const static int _mut_definition = 0; //memberof FunctionalNode: mutable definition
			const static int _mut_assign = 1; //memberof FunctionalNode: mutable assignment
			const static int _value = 2; //memberof FunctionalNode: literal value
			const static int _arith = 3; //memberof FunctionalNode: binary/unary operation
			const static int _call = 4; //memberof FunctionalNode: function call
			const static int _if = 5; //memberof FunctionalNode: if statement
			const static int _forl = 6; //memberof FunctionalNode: for loop

		private:
			int type;

			shared_ptr<MutableDefinition> md;
			shared_ptr<MutableAssignment> ma;
			shared_ptr<LiteralValue> val;
			shared_ptr<Operation> arith;
			shared_ptr<Call> call;
			shared_ptr<IfStatement> ifs;
			shared_ptr<ForLoop> forl;

		public:
			FunctionalNode(); //methodof FunctionalNode: initializer
			FunctionalNode(MutableDefinition d); //methodof FunctionalNode: initializer with mutable definition
			FunctionalNode(MutableAssignment a); //methodof FunctionalNode: initializer with mutable assignment
			FunctionalNode(LiteralValue v); //methodof FunctionalNode: initializer with literal value
			FunctionalNode(Operation o); //methodof FunctionalNode: initializer with binary/unary operation
			FunctionalNode(Call c); //methodof FunctionalNode: initializer with function call
			FunctionalNode(IfStatement i); //methodof FunctionalNode: initializer with if statement
			FunctionalNode(ForLoop f); //methodof FunctionalNode: initializer with for loop
			bool initialized(); //methodof FunctionalNode: checks to see if node has been initialized
			int getType(); //methodof FunctionalNode: gets the type (i.e. _forl for for loop)
			string getTypeString(); //methodof FunctionalNode: gets a string representation of the type (i.e. "6 (for loop)" for _forl)
			MutableDefinition getMutableDefinition(); //methodof FunctionalNode: gets the mutable definition
			MutableAssignment getMutableAssignment(); //methodof FunctionalNode: gets the mutable assignment
			LiteralValue getValue(); //methodof FunctionalNode: gets the literal value
			Operation getOperation(); //methodof FunctionalNode: gets the binary/unary operation
			Call getCall(); //methodof FunctionalNode: gets the function call
			IfStatement getIfStatement(); //methodof FunctionalNode: gets the if statement
			ForLoop getForLoop(); //methodof FunctionalNode: gets the for loop
			Value *gen(VM &vm); //methodof FunctionalNode: generates LLVM expression
			string display(string pref); //methodof FunctionalNode: displays with prefix 'pref'
			string display(); //methodof FunctionalNode: just displays
		};

		//class MutableDefinition: expression to define a new mutable object
		class MutableDefinition
		{
		private:
			LiteralType type;
			string name;
			FunctionalNode init;

		public:
			MutableDefinition(); //methodof MutableDefinition: initializer
			MutableDefinition(LiteralType t, string n); //methodof MutableDefinition: initializer with type and name (like: int x)
			MutableDefinition(LiteralType t, string n, FunctionalNode v); //methodof MutableDefinition: initializer with type, name, and value (like: int x = 5)
			bool initialized(); //methodof MutableDefinition: checks to see if the definition is initialized
			LiteralType getType(); //methodof MutableDefinition: gets the type
			string getName(); //methodof MutableDefinition: gets the name
			FunctionalNode getValue(); //methodof MutableDefinition: gets the value
			Value *gen(VM &vm); //methodof MutableDefinition: generates the LLVM expression as well as appends the allocation instance to the parent function
			string display(string pref); //methodof MutableDefinition: displays with prefix 'pref'
			string display(); //methodof MutableDefinition: just displays
		};

		//class MutableAssignment: expression to set the value of a mutable object
		class MutableAssignment
		{
		private:
			string name;
			FunctionalNode val;

		public:
			MutableAssignment(); //methodof MutableAssignment: initializer
			MutableAssignment(string n, FunctionalNode v); //methodof MutableAssignment: initializer with name and value (like: x = 5)
			bool initialized(); //methodof MutableAssignment: checks to see if assignment is initialized
			string getName(); //methodof MutableAssignment: gets the variable name
			FunctionalNode getValue(); //methodof MutableAssignment: gets the value
			Value *gen(VM &vm); //methodof MutableAssignment: generate the LLVM value as well as appends the assignment assembly
			string display(string pref); //methodof MutableAssignment: displays with prefix 'pref'
			string display(); //methodof MutableAssignment: just displays
		};

		//class Operation: binary/unary operation expression
		class Operation
		{
		private:
			int op;
			FunctionalNode left;
			FunctionalNode right;

		public:
			const static int uninitialized = -1; //memberof Operation: null operation (causes error)
			const static int iadd = 0; //memberof Operation: integer addition (like 5 + 3)
			const static int fadd = 1; //memberof Operation: floating addition (like 5.2 + 3)
			const static int isub = 2; //memberof Operation: integer subtraction (like 5 - 3)
			const static int fsub = 3; //memberof Operation: floating subtraction (like 5.2 - 3)
			const static int imul = 4; //memberof Operation: integer multiplication (like 5 * 3)
			const static int fmul = 5; //memberof Operation: floating multiplication (like 5.2 * 3)
			const static int idiv = 6; //memberof Operation: integer division with truncation (like 5 / 3 == 1)
			const static int fdiv = 7; //memberof Operation: floating division (like 5.2 / 2 == 2.6)
			const static int iand = 8; //memberof Operation: and (like 1 & 0 == 0, 1 & 1 == 1)
			const static int ior = 9; //memberof Operation: or (like 1 | 0 == 1, 0 | 0 == 0)
			const static int ixor = 10; //memberof Operation: exclusive or (like 1 ^ 1 == 0, 1 ^ 0 == 1)
			const static int ilt = 11; //memberof Operation: integer less-than (like 1 < 2)
			const static int flt = 12; //memberof Operation: floating less-than (like 1.2 < 2)
			const static int igt = 13; //memberof Operation: integer greater-than (like 2 > 1)
			const static int fgt = 14; //memberof Operation: floating greater-than (like 2 > 1.2)
			const static int ile = 15; //memberof Operation: integer less-than-or-equal-to (like 1 <= 2)
			const static int fle = 16; //memberof Operation: floating less-than-or-equal-to (like 1.2 <= 2)
			const static int ige = 17; //memberof Operation: integer greater-than-or-equal-to (like 2 >= 1)
			const static int fge = 18; //memberof Operation: floating greater-than-or-equal-to (like 2 >= 1.2)
			const static int ieq = 19; //memberof Operation: integer equal to (like 1 == 1)
			const static int feq = 20; //memberof Operation: floating equal to (like 1.2 == 1.2)
			const static int ine = 21; //memberof Operation: integer not equal to (like 1 != 2)
			const static int fne = 22; //memberof Operation: floating not equal to (like 1.2 != 2)
			const static int ineg = 23; //memberof Operation: negation (like !1 == 0)
			const static int casti8 = 24; //memberof Operation: cast to char
			const static int casti32 = 25; //memberof Operation: cast to int
			const static int castf32 = 26; //memberof Operation: cast to float
			const static int castxi8 = 27; //memberof Operation: cast to char *
			const static int castxi32 = 28; //memberof Operation: cast to int *
			const static int castxf32 = 29; //memberof Operation: cast to float *
			const static int derefxi8 = 30; //memberof Operation: derefence character reference
			const static int derefxi32 = 31; //memberof Operation: derefence integer reference
			const static int derefxf32 = 32; //memberof Operation: derefence floating reference
			const static int xaddr = 33; //memberof Operation: get address of object

			Operation(); //methodof Operation: initializer
			Operation(FunctionalNode l, int o, FunctionalNode r); //methodof Operation: binary initializer
			Operation(int o, FunctionalNode r); //methodof Operation: unary initializer
			bool initialized(); //methodof Operation: checks to see if operation is intialized
			int getOp(); //methodof Operation: gets the operation id (i.e. iadd)
			FunctionalNode getLeft(); //methodof Operation: gets the left expression
			FunctionalNode getRight(); //methodof Operation: gets the right expression
			Value *gen(VM &vm); //methodof Operation: generate LLVM expression
			string display(string pref); //methodof Operation: displays with prefix 'pref'
			string display(); //methodof Operation: just displays
		};

		//class Call: function call expression
		class Call
		{
		private:
			string func;
			vector<FunctionalNode> args;

		public:
			Call(); //methodof Call: initializer
			Call(string f, vector<FunctionalNode> a); //methodof Call: initializer with function name and arguments
			bool initialized(); //methodof Call: checks to see if call is initialized
			string getName(); //methodof Call: gets the function name
			Value *gen(VM &vm); //methodof Call: generates the LLVM expression
			string display(); //methodof Call: displays with prefix 'pref'
			string display(string pref); //methodof Call: just displays
		};

		//class IfStatement: if statement expression
		class IfStatement
		{
		private:
			bool init;
			FunctionalNode cond;
			FunctionalNode then;
			FunctionalNode other;

		public:
			IfStatement(); //methodof IfStatement: initializer
			IfStatement(FunctionalNode c, FunctionalNode t, FunctionalNode e); //methodof IfStatement: initializer with condition 'c', then expression 't', and else expression 'e'
			bool initialized(); //methodof IfStatement: checks to see if statement is initialized
			FunctionalNode getCondition(); //methodof IfStatement: gets the condition
			FunctionalNode getThen(); //methodof IfStatement: gets the then expression
			FunctionalNode getElse(); //methodof IfStatement: gets the else expression
			Value *gen(VM &vm); //methodof IfStatement: generates LLVM construct
			string display(string pref); //methodof IfStatement: displays with prefix 'pref'
			string display(); //methodof IfStatement: just displays
		};

		//class ForLoop: for loop expression
		class ForLoop
		{
		private:
			string var;
			FunctionalNode init;
			FunctionalNode end;
			FunctionalNode step;
			FunctionalNode body;

		public:
			ForLoop(); //methodof ForLoop: initializer
			ForLoop(string v, FunctionalNode i, FunctionalNode e, FunctionalNode s, FunctionalNode b); //methodof ForLoop: initializer with variable name 'v', initial value 'i', end value 'e', step value 's', and body 'b'
			bool initialized(); //methodof ForLoop: checks to see if the loop is initialized
			string getVariable(); //methodof ForLoop: gets the variable name
			FunctionalNode getInitial(); //methodof ForLoop: gets the initial value
			FunctionalNode getFinal(); //methodof ForLoop: gets the final value
			FunctionalNode getStep(); //methodof ForLoop: gets the step value
			FunctionalNode getBody(); //methodof ForLoop: gets the body expression
			Value *gen(VM &vm); //methodof ForLoop: generates the LLVM construct
			string display(string pref); //methodof ForLoop: displays with prefix 'pref'
			string display(); //methodof ForLoop: just displays
		};

		//class Prototype: function prototype
		class Prototype
		{
		private:
			LiteralType rtn;
			vector<pair<LiteralType, string> > args;
			string name;

		public:
			Prototype(); //methodof Prototype: initializer
			Prototype(string n, LiteralType r, vector<pair<LiteralType, string> > a); //methodof Prototype: initializer with name, return type, and argument types/names
			bool initialized(); //methodof Prototype: checks to see if prototype is initialized
			unsigned int size(); //methodof Prototype: gets the number of arguments
			LiteralType getReturnType(); //methodof Prototype: gets the return type
			string getName(); //methodof Prototype: gets the function name
			string getArgumentName(unsigned int idx); //methodof Prototype: gets the name of argument at index 'idx'
			LiteralType getArgumentType(unsigned int idx); //methodof Prototype: gets the type of argumetn at index 'idx'
			Function *gen(VM &vm); //methodof Prototype: generates the LLVM decleration
			string display(string pref); //methodof Prototype: displays with prefix 'pref'
			string display(); //methodof Prototype: just displays
		};

		//class Body: function body
		class Body
		{
		private:
			Prototype ptype;
			vector<FunctionalNode> nodes;

		public:
			Body(); //methodof Body: initializer
			Body(Prototype p); //methodof Body: initializer with prototype
			bool initialized(); //methodof Body: checks to see if body was initialized with prototype
			Prototype getPrototype(); //methodof Body: gets the prototype
			int appendFunctionalNode(FunctionalNode n); //methodof Body: adds an expression to the function
			Function *gen(VM &vm); //methodof Body: generates the LLVM function
			string display(string pref); //methodof Body: displays with prefix 'pref'
			string display(); //methodof Body: just displays
		};

		//class VM: the -_- virtual machine
		class VM
		{
		public:
			shared_ptr<Module> module; //memberof VM: the LLVM module (internal use only)
			shared_ptr<IRBuilder<> > builder; //memberof VM: the LLVM builder class (internal use only)
			shared_ptr<FunctionPassManager> fpm; //memberof VM: the LLVM function pass manager (internal use only)
			ExecutionEngine *ee; //memberof VM: the LLVM execution engine (internal use only)
			map<string, AllocaInst *> variables;  //memberof VM: the allocation instance variable mapping (mutable objects)
			map<string, Function *> funcs; //memberof VM: the function mapping

			AllocaInst *createEntryBlockAlloca(Function *f, string n, Type *t); //memberof VM: create a new enter block allocation instance (internal use only)
			Value *loadMutable(string n); //memberof VM: generate assembly to load a mutable object (internal use only)
			Value *storeMutable(string n, Value *v); //memberof VM: generate assembly to store a mutable object (internal use only)

			VM(); //methodof VM: initializer
			~VM(); //methodof VM: uninitializer
			int setOptimizationLevel(int l); //methodof VM: sets the optimization level (can be 0 or 1 - REQUIRED)
			int linkExternal(Prototype p, void *f); //methodof VM: link an external function 'f' with prototype 'p' to virtual machine
			void *JIT(Body b, bool d); //methodof VM: JIT compile function with body 'b' (outputs debug info if 'd' == 'true')
			void *JIT(Body b);  //methodof VM: JIT compile function with body 'b' (no debugging)
			vector<string> listJITedFunctions(); //methodof VM: returns a list of all JIT compiled functions
			int unJIT(string n); //methodof VM: removes the function named 'n' from the virtual machine (safes memory space of function)
		};
	}
}

/// Here's an example of how to make a function to increment stuff with the -_- VM and call it from with in the C++ parent program.
/// {{VM vm = VM();
/// vm.setOptimizationLevel(1); // this is required to initialize the opimizer.
/// // if you don't want optimization pass a level of 0.
///
/// // let's create the argument of the function for the prototype
/// pair&lt;LiteralType, string&gt; arg;
/// arg.first = LiteralType(LiteralType::_int);
/// arg.second = "x";
///
/// // now let's make the argument list for the prototype
/// vector&lt;pair&lt;LiteralType, string&gt; &gt; args;
/// args.push_back(arg);
///
/// // time to make the actual prototype itself
/// Prototype proto = Prototype("increment", LiteralType(LiteralType::_int), args);
///
/// // proto is now a function called "increment" which takes one integer argument called "x" and returns an integer
/// Body body = Body(proto);
///
/// // Now we need an operation to add one to the argument
/// LiteralValue left = LiteralValue("x"); // this is a reference to a variable named "x"
/// LiteralValue right = LiteralValue(LiteralInt(1)); // this is a constant integer equaling 1
/// Operation op = Operation(left, Operation::iadd, right); // add the two integers
///
/// // Let's put the operation in the function
/// body.appendFunctionalNode(op); // the last node appended to a function automatically becomes the return value
///
/// // Compile and cast!
/// typedef int (*myFunctionType)(int);
/// myFunctionType increment = (myFunctionType)vm.JIT(body);
///
/// // Call!
/// printf("%i\n", increment(5)); // should print 6}}

#endif